#include "Project.h"

#include <QFontMetrics>
#include <QPainter>
#include <QFile>
#include <QXmlStreamWriter>
#include <QXmlStreamReader>
#include <QBuffer>
#include <QDebug>

template<typename T>
void LOCAL_Write( QIODevice & device, const T value )
{
    device.write( (const char *) &value, sizeof( T ) );
}

Project::Project() :
    Font(),
    Texture(),
    GlyphTable(),
    FontIsOutlined( false )
{

}

Project::~Project()
{

}

int Project::rowCount( const QModelIndex & parent ) const
{
    if( parent.isValid() )
    {
        return 0;
    }

    return GlyphTable.size();
}

int Project::columnCount( const QModelIndex & parent ) const
{
    if( parent.isValid() )
    {
        return 0;
    }

    return 4;
}

QVariant Project::data(const QModelIndex & index, int role ) const
{
    if( index.parent().isValid() )
    {
        return QVariant();
    }

    if( index.row() >= 0 && index.row() < GlyphTable.size() )
    {
        const Glyph & glyph = GlyphTable[ index.row() ];

        if( role == Qt::DisplayRole )
        {
            switch( index.column() )
            {
                case 0:
                {
                    return glyph.Code.unicode();
                }

                case 1:
                {
                    return QString( "t:%0,l:%1,b:%2,r:%3" )
                        .arg( glyph.CharacterRect.top() )
                        .arg( glyph.CharacterRect.left() )
                        .arg( glyph.CharacterRect.bottom() )
                        .arg( glyph.CharacterRect.right() );
                }

                case 2:
                {
                    return glyph.Width;
                }

                case 3:
                {
                    return QString( "t:%0,l:%1,b:%2,r:%3" )
                        .arg( glyph.TextureRect.top() )
                        .arg( glyph.TextureRect.left() )
                        .arg( glyph.TextureRect.bottom() )
                        .arg( glyph.TextureRect.right() );
                }
            }
        }
        else if( role == Qt::DecorationRole )
        {
            switch( index.column() )
            {
                case 0:
                {
                    QPixmap
                        decoration_role( 32, 32 );
                    QPainter
                        painter( &decoration_role );

                    decoration_role.fill( Qt::black );
                    painter.drawPixmap( QPoint( 0, 0 ), glyph.Pixmap );
                    return decoration_role;
                }
            }
        }
        else if( role == PixmapRole )
        {
            switch( index.column() )
            {
                case 0:
                {
                    return glyph.Pixmap;
                }
            }
        }
        if( role == RectRole )
        {
            switch( index.column() )
            {
                case 1:
                {
                    return glyph.CharacterRect;
                }

                case 3:
                {
                    return glyph.TextureRect;
                }
            }
        }
    }

    return QVariant();
}

bool Project::removeRows(int row, int count, const QModelIndex & parent)
{
    if( parent.isValid() )
    {
        return false;
    }

    if( row >= 0 && ( row + count ) <= GlyphTable.size() )
    {
        beginResetModel();
        
        for( int i = 0; i < count; ++i )
        {
            GlyphTable.remove( row + i );
        }

        updateTexture();

        endResetModel();

        return true;
    }

    return false;
}

void Project::setFont( const QFont & font )
{
    QFontMetrics
        font_metrics( font );

    Font = font;

    for( int character_index = 0; character_index < 256; ++character_index )
    {
        if( font_metrics.inFont( QChar( character_index ) ) )
        {
            GlyphTable.push_back( Glyph() );
            createGlyph( GlyphTable.back(), character_index );
        }
    }

    updateTexture();
}

void Project::addCharacter( int unicode )
{
    beginResetModel();
    {
        GlyphTable.push_back( Glyph() );
        createGlyph( GlyphTable.back(), unicode );
        updateTexture();
    }
    endResetModel();
}

void Project::addCustomCharacter( int unicode, const QPixmap & pixmap, int start, int advance, int baseline )
{
    beginResetModel();
    {
        Glyph 
            glyph;

        glyph.IsCustom = true;
        glyph.Code = unicode;
        glyph.Pixmap = pixmap;
        glyph.CharacterRect = QRect( QPoint( 0, 0 ), pixmap.size() );
        glyph.CharacterRect.translate( -start, -baseline );
        glyph.Width = advance;

        GlyphTable.push_back( glyph );
        updateTexture();
    }
    endResetModel();
}

bool Project::getCustomCharacter( int index, int & unicode, QPixmap & pixmap, int & start, int & advance, int & baseline )
{
    if( index < 0 || index >= GlyphTable.count() )
    {
        return false;
    }

    const Glyph & glyph = GlyphTable[ index ];

    if( glyph.IsCustom )
    {
        unicode = glyph.Code.unicode();
        pixmap = glyph.Pixmap;
        start = -glyph.CharacterRect.x();
        advance = glyph.Width;
        baseline = -glyph.CharacterRect.y();
        return true;
    }

    return false;
}

void Project::updateCustomCharacter( int index, int unicode, const QPixmap & pixmap, int start, int advance, int baseline )
{
    beginResetModel();
    {
        Glyph 
            & glyph = GlyphTable[ index ];

        glyph.IsCustom = true;
        glyph.Code = unicode;
        glyph.Pixmap = pixmap;
        glyph.CharacterRect = QRect( QPoint( 0, 0 ), pixmap.size() );
        glyph.CharacterRect.translate( -start, -baseline );
        glyph.Width = advance;

        updateTexture();
    }
    endResetModel();
}

void Project::save( QIODevice & file )
{
    QXmlStreamWriter
        writer( &file );

    writer.setAutoFormatting( true );

    writer.writeStartDocument();
    {
        writer.writeStartElement( "FontShakerProject" );
        {
            writer.writeTextElement( "Font", Font.toString() );

            writer.writeStartElement( "GlyphTable" );
            {
                foreach( const Glyph & glyph, GlyphTable )
                {
                    if( glyph.IsCustom )
                    {
                        QBuffer
                            image_buffer;

                        image_buffer.open( QIODevice::WriteOnly );
                        glyph.Pixmap.save( &image_buffer, "PNG" );

                        writer.writeStartElement( "CustomGlyph" );
                        {
                            writer.writeAttribute( "code", QString::number( glyph.Code.unicode() ) );
                            writer.writeTextElement( "Image", image_buffer.data().toBase64() );
                            writer.writeTextElement( "Width", QString::number( glyph.Width ) );
                            writer.writeStartElement( "Rect" );
                            {
                                writer.writeTextElement( "Left", QString::number( glyph.CharacterRect.left() ) );
                                writer.writeTextElement( "Right", QString::number( glyph.CharacterRect.right() ) );
                                writer.writeTextElement( "Top", QString::number( glyph.CharacterRect.top() ) );
                                writer.writeTextElement( "Bottom", QString::number( glyph.CharacterRect.bottom() ) );
                            }
                            writer.writeEndElement();
                        }
                        writer.writeEndElement();
                    }
                    else
                    {
                        writer.writeStartElement( "Glyph" );
                        writer.writeAttribute( "code", QString::number( glyph.Code.unicode() ) );
                        writer.writeEndElement();
                    }
                }
            }
            writer.writeEndElement();
        }
        writer.writeEndElement();
    }
    writer.writeEndDocument();
}

bool Project::load( QIODevice & file )
{
    QXmlStreamReader
        reader( &file );
    
    if( !reader.readNextStartElement() && reader.tokenString() != "FontShakerProject" )
    {
        //:TODO: handle error;
        return false;
    }

    while( !reader.atEnd() )
    {
        switch( reader.readNext() )
        {
            case QXmlStreamReader::Characters:
            {
                Q_ASSERT( reader.isWhitespace() );
            }
            break;

            case QXmlStreamReader::StartElement:
            {
                if( reader.name() == "Font" )
                {
                    if( !Font.fromString( reader.readElementText() ) )
                    {
                        return false;
                    }
                }
                else if( reader.name() == "GlyphTable" )
                {
                    reader.readNext();

                    while( reader.tokenType() != QXmlStreamReader::EndElement )
                    {
                        bool
                            ok;
                        int
                            code;
                        QStringRef
                            code_value;

                        while( reader.tokenType() == QXmlStreamReader::Characters )
                        { 
                            Q_ASSERT( reader.isWhitespace() );
                            reader.readNext();
                        }

                        Q_ASSERT( reader.tokenType() == QXmlStreamReader::StartElement && (
                            reader.name() == "Glyph" || reader.name() == "CustomGlyph" ) );

                        code_value = reader.attributes().value( "code" );

                        code = code_value.toString().toInt( &ok );

                        if( ok )
                        {
                            if( reader.name() == "Glyph" )
                            {
                                GlyphTable.push_back( Glyph() );
                                createGlyph( GlyphTable.back(), code );

                                reader.readNext();

                                while( reader.tokenType() == QXmlStreamReader::Characters )
                                { 
                                    Q_ASSERT( reader.isWhitespace() );

                                    reader.readNext();
                                }

                                Q_ASSERT( reader.tokenType() == QXmlStreamReader::EndElement );
                            }
                            else
                            {
                                Glyph 
                                    glyph;

                                glyph.IsCustom = true;
                                glyph.Code = code;

                                reader.readNext();

                                while( reader.tokenType() == QXmlStreamReader::Characters )
                                { 
                                    Q_ASSERT( reader.isWhitespace() );
                                    reader.readNext();
                                }

                                Q_ASSERT( reader.tokenType() == QXmlStreamReader::StartElement );

                                while( reader.tokenType() != QXmlStreamReader::EndElement )
                                {
                                    if( reader.name() == "Image" )
                                    {
                                        QByteArray
                                            image_data;

                                        image_data = QByteArray::fromBase64( reader.readElementText().toAscii() );
                                        glyph.Pixmap.loadFromData( image_data, "PNG" );
                                    }
                                    else if( reader.name() == "Width" )
                                    {
                                        glyph.Width = reader.readElementText().toInt( &ok );

                                        Q_ASSERT( ok );
                                    }
                                    else if( reader.name() == "Rect" )
                                    {
                                        reader.readNext();

                                        while( reader.tokenType() == QXmlStreamReader::Characters )
                                        { 
                                            Q_ASSERT( reader.isWhitespace() );
                                            reader.readNext();
                                        }

                                        Q_ASSERT( reader.tokenType() == QXmlStreamReader::StartElement );

                                        while( reader.tokenType() != QXmlStreamReader::EndElement )
                                        {
                                            if( reader.name() == "Left" )
                                            {
                                                glyph.CharacterRect.setLeft( reader.readElementText().toInt( &ok ) );

                                                Q_ASSERT( ok );
                                            }
                                            else if( reader.name() == "Right" )
                                            {
                                                glyph.CharacterRect.setRight( reader.readElementText().toInt( &ok ) );

                                                Q_ASSERT( ok );
                                            }
                                            else if( reader.name() == "Top" )
                                            {
                                                glyph.CharacterRect.setTop( reader.readElementText().toInt( &ok ) );

                                                Q_ASSERT( ok );
                                            }
                                            else if( reader.name() == "Bottom" )
                                            {
                                                glyph.CharacterRect.setBottom( reader.readElementText().toInt( &ok ) );

                                                Q_ASSERT( ok );
                                            }

                                            qDebug() << reader.name().toString() << "\n";

                                            reader.readNext();

                                            while( reader.tokenType() == QXmlStreamReader::Characters )
                                            { 
                                                Q_ASSERT( reader.isWhitespace() );

                                                reader.readNext();
                                            }
                                        }
                                    }

                                    reader.readNext();

                                    while( reader.tokenType() == QXmlStreamReader::Characters )
                                    { 
                                        Q_ASSERT( reader.isWhitespace() );

                                        reader.readNext();
                                    }
                                }
                                
                                GlyphTable.push_back( glyph );
                            }
                        }
                        else
                        {
                            Q_ASSERT( "Non numeric code" );
                        }

                        reader.readNext();

                        while( reader.tokenType() == QXmlStreamReader::Characters )
                        { 
                            Q_ASSERT( reader.isWhitespace() );

                            reader.readNext();
                        }
                    }
                }
                else 
                {
                    Q_ASSERT( !"Unsupported tag" );
                }
            }
            break;

            case QXmlStreamReader::EndElement:
            {
                Q_ASSERT( reader.name() == "FontShakerProject" );
            }
            break;

            case QXmlStreamReader::EndDocument:
            {
                updateTexture();
                return true;
            }
            break;

            default:
                Q_ASSERT( !":TODO:" );
        }
    }
    
    return false;
}

void Project::saveInABCFormat( const QString & abc_file_path, const QString & texture_file_path )
{
    QFile
        abc_file( abc_file_path );
    QFontMetrics
        font_metrics( Font );
    const int
        font_info_version = 5;

    if ( !abc_file.open( QIODevice::WriteOnly ) )
        return;

    LOCAL_Write( abc_file, font_info_version );

    float font_height = float( font_metrics.height() );
    LOCAL_Write( abc_file, font_height );

    // Write out the vertical padding caused by effects
    float top_padding    = ( FontIsOutlined ? 1.0f : 0.0f );
    float bottom_padding = ( FontIsOutlined ? 1.0f : 0.0f ); //( m_bOutlineEffect ? ( m_bShadowEffect ? 2.0f : 1.0f ) : ( m_bShadowEffect ? 2.0f : 0.0f ) );
    float font_y_advance  = font_height - top_padding - bottom_padding;

    LOCAL_Write( abc_file, top_padding );
    LOCAL_Write( abc_file, bottom_padding );
    LOCAL_Write( abc_file, font_y_advance );

    // Write the translator table
    {
        short
            maximum_glyph,
            * translation_table;

        maximum_glyph = 0;

        foreach( const Glyph & glyph, GlyphTable )
        {
            if( maximum_glyph < glyph.Code.unicode() )
            {
                maximum_glyph = glyph.Code.unicode();
            }
        }

        LOCAL_Write( abc_file, maximum_glyph );

        translation_table = ( short* ) alloca( ( maximum_glyph + 1 ) * sizeof( short ) );

        memset( translation_table, 0, ( maximum_glyph + 1 ) * sizeof( short ) );

        for( int glyph_index = 0; glyph_index <  GlyphTable.size(); ++glyph_index )
        {
            translation_table[ GlyphTable[ glyph_index ].Code.unicode() ] = glyph_index + 1;
        }

        abc_file.write( (const char*)translation_table, ( maximum_glyph + 1 ) * sizeof( short ) );

    }

    // Write the glyph attributes to the file
    {
        int glyph_count = GlyphTable.size();

        LOCAL_Write( abc_file, glyph_count + 1 );

        {
            const Glyph & glyph = GlyphTable[ 0 ];
            // Character 0
            short left, top, right, bottom;
            short offset, width, advance, mask;

            left = glyph.TextureRect.left();
            top = glyph.TextureRect.top();
            right = glyph.TextureRect.right() + 1;
            bottom = glyph.TextureRect.bottom() + 1;

            offset = glyph.CharacterRect.left();
            width = glyph.CharacterRect.width(); 
            advance = glyph.Width;
            mask = 0;

            LOCAL_Write( abc_file, left );
            LOCAL_Write( abc_file, top );
            LOCAL_Write( abc_file, right );
            LOCAL_Write( abc_file, bottom );

            LOCAL_Write( abc_file, offset );
            LOCAL_Write( abc_file, width );
            LOCAL_Write( abc_file, advance );
            LOCAL_Write( abc_file, mask );
        }

        foreach( const Glyph & glyph, GlyphTable )
        {
            short left, top, right, bottom;
            short offset, width, advance, mask;

            left = glyph.TextureRect.left();
            top = glyph.TextureRect.top();
            right = glyph.TextureRect.right() + 1;
            bottom = glyph.TextureRect.bottom() + 1;
 
            offset = glyph.CharacterRect.left();
            width = glyph.CharacterRect.width(); 
            advance = glyph.Width;
            mask = 0;

            LOCAL_Write( abc_file, left );
            LOCAL_Write( abc_file, top );
            LOCAL_Write( abc_file, right );
            LOCAL_Write( abc_file, bottom );

            LOCAL_Write( abc_file, offset );
            LOCAL_Write( abc_file, width );
            LOCAL_Write( abc_file, advance );
            LOCAL_Write( abc_file, mask );
        }
    }

    Texture.save( texture_file_path );
}

void Project::createGlyph( Glyph & glyph, QChar code )
{
    QFontMetrics
        font_metrics( Font );
    QRect
        rect;
    int
        font_height,
        ascent,
        left_padding,
        right_padding,
        top_padding,
        bottom_padding;

    left_padding   = ( FontIsOutlined ? 1 : 0 );
    right_padding  = ( FontIsOutlined ? 1 : 0 );
    top_padding    = ( FontIsOutlined ? 1 : 0 );
    bottom_padding = ( FontIsOutlined ? 1 : 0 );

    font_height = font_metrics.height();
    ascent = font_metrics.ascent();
        
    rect = font_metrics.boundingRect( code );
    rect.adjust( -1 - left_padding, -1 - top_padding, 1 + right_padding, 1 + bottom_padding );
    rect.setTop( -ascent - top_padding );
    rect.setBottom( font_height - ascent + bottom_padding );
    
    glyph.IsCustom = false;
    glyph.Code = code;
    glyph.CharacterRect = rect;
    glyph.Width = font_metrics.width( code ) + left_padding + right_padding;

    glyph.Pixmap = QPixmap( glyph.CharacterRect.size() );
    glyph.Pixmap.fill( QColor( 0, 0, 0, 0 ) );
    
    QPainter painter( &glyph.Pixmap );
    painter.setFont( Font );
    painter.setRenderHint( QPainter::TextAntialiasing, false );

    if( FontIsOutlined )
    {
        painter.setPen( Qt::black );
        painter.drawText( QPoint( -rect.left() - 1, -rect.top() - 1 ), QString( code ) );
        painter.drawText( QPoint( -rect.left() - 1, -rect.top() + 0 ), QString( code ) );
        painter.drawText( QPoint( -rect.left() - 1, -rect.top() + 1 ), QString( code ) );
        painter.drawText( QPoint( -rect.left() - 0, -rect.top() - 1 ), QString( code ) );
        painter.drawText( QPoint( -rect.left() - 0, -rect.top() + 0 ), QString( code ) );
        painter.drawText( QPoint( -rect.left() - 0, -rect.top() + 1 ), QString( code ) );
        painter.drawText( QPoint( -rect.left() + 1, -rect.top() - 1 ), QString( code ) );
        painter.drawText( QPoint( -rect.left() + 1, -rect.top() + 0 ), QString( code ) );
        painter.drawText( QPoint( -rect.left() + 1, -rect.top() + 1 ), QString( code ) );    
    }

    painter.setPen( Qt::white );
    painter.drawText( QPoint( -rect.left(), -rect.top() ), QString( code ) );
}

void Project::updateTexture()
{
    QPoint
        current_line;
    const int
        maximum_texture_length = 512;
    int
        current_line_height,
        texture_height;
        
    current_line.setX( 0 );
    current_line.setY( 0 );
    
    current_line_height = 0;
    
    for( int glyph_index = 0; glyph_index < GlyphTable.size(); ++glyph_index )
    {
        QSize
            pixmap_size;
        Glyph 
            & glyph = GlyphTable[ glyph_index ];
            
        pixmap_size = glyph.Pixmap.size();
        
        if( pixmap_size.width() + current_line.x() + 3 > maximum_texture_length )
        {
            current_line.setX( 0 );
            current_line.ry() += current_line_height + 3;
            current_line_height = 0;
        }
        
        glyph.TextureRect = QRect( current_line, pixmap_size );
        
        current_line_height = qMax( current_line_height, pixmap_size.height() );
        current_line.rx() += pixmap_size.width() + 3;
    }

    texture_height = current_line.y() + current_line_height;

    // round to next power of two

    texture_height |= texture_height >> 1;
    texture_height |= texture_height >> 2;
    texture_height |= texture_height >> 4;
    texture_height |= texture_height >> 8;
    texture_height |= texture_height >> 16;
    ++texture_height;
    
    Texture = QPixmap( maximum_texture_length, texture_height );
    Texture.fill( QColor( 0, 0, 0, 0 ) );
    
    QPainter painter( &Texture );

    foreach( const Glyph & glyph, GlyphTable )
    {
        painter.drawPixmap( glyph.TextureRect, glyph.Pixmap );
    }
}